package com.gmrz.uaf.servlet.v1.validation;

import com.gmrz.uaf.common.UAFRuntimeException;
import com.gmrz.uaf.protocol.v1.schema.AuthenticationResponse;
import com.gmrz.uaf.protocol.v1.schema.DeviceDetails;
import com.gmrz.uaf.protocol.v1.schema.RegistrationResponse;
import com.gmrz.uaf.protocol.v1.schema.Version;
import com.gmrz.util.Convert;
import com.gmrz.util.Strings;
import org.apache.commons.codec.binary.Base64;

import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class RestValidator {
    public static final int USERNAME_LENGTH = 128;
    public static final int TRANSACTION_TEXT_LENGTH = 4096;
    public static final int POLICY_NAME_LENGTH = 256;
    public static final int UAFRESPONSE_LENGTH = 500000;
    public static final int KEY_ID_LENGTH = 100;
    public static final int AAID_LENGTH = 9;
    public static final int UVI_LENGTH = 44;
    public static final int UVI_STATUS_LENGTH = 1;
    public static final int TRANSNO_LENGTH = 128;
    public static final String AAID_RESTRICTION_REGEX = ".{4}#.{4}";
    private static Map<String, ValidationCriteria<?>> validationMap = loadValidationMap();

    public static Map<String, ValidationCriteria<?>> loadValidationMap() {
        Map<String, ValidationCriteria<?>> validationMap = new HashMap<String, ValidationCriteria<?>>();

        validationMap.put("deviceID", new ValidationCriteria<String>(String.class, 128, null, false, true));//"^[a-zA-Z0-9]$"
        validationMap.put("transType", new ValidationCriteria<String>(String.class, 2, "^[0-9]{2}$", false, true));
        validationMap.put("authType", new ValidationCriteria<String>(String.class, 2, "^[0-9]{2}$", false, true));
        validationMap.put("from", new ValidationCriteria<String>(String.class, 2, "^[0-1]{2}$", false, false));
        validationMap.put("opType", new ValidationCriteria<String>(String.class, 2, "^[0-9]{2}$", false, true));

        validationMap.put("appID", new ValidationCriteria<String>(String.class, 128, null, false, true));

        validationMap.put("transactionText", new ValidationCriteria<Base64>(Base64.class, TRANSACTION_TEXT_LENGTH, null, false, false));

        validationMap.put("userName", new ValidationCriteria<String>(String.class, USERNAME_LENGTH, null, false, true));

        validationMap.put("policyName", new ValidationCriteria<String>(String.class, POLICY_NAME_LENGTH, null, false, true));

        validationMap.put("uafResponse", new ValidationCriteria<String>(String.class, UAFRESPONSE_LENGTH, null, false, true));

        validationMap.put("keyID", new ValidationCriteria<Base64>(Base64.class, KEY_ID_LENGTH, null, false, true));

        validationMap.put("aaid", new ValidationCriteria<String>(String.class, AAID_LENGTH, AAID_RESTRICTION_REGEX, false, true));

        validationMap.put("uvi", new ValidationCriteria<Base64>(Base64.class, UVI_LENGTH, null, false, true));

        validationMap.put("uviStatus", new ValidationCriteria<Integer>(Integer.class, UVI_STATUS_LENGTH, "^[0-4]$", false, true));

        validationMap.put("transNo", new ValidationCriteria<String>(String.class, TRANSNO_LENGTH, null, false, true));
        validationMap.put("deviceName", new ValidationCriteria<String>(String.class, LENGTH_64, null, false, true));
        validationMap.put("deviceAliasName", new ValidationCriteria<String>(String.class, TRANSNO_LENGTH, null, true, false));
        validationMap.put("deviceType", new ValidationCriteria<String>(String.class, LENGTH_64, null, false, true));
        validationMap.put("osType", new ValidationCriteria<String>(String.class, LENGTH_16, null, false, true));
        validationMap.put("osVersion", new ValidationCriteria<String>(String.class, LENGTH_64, null, false, true));

        validationMap.put("rf1", new ValidationCriteria<String>(String.class, LENGTH_64, null, true, false));
        validationMap.put("rf2", new ValidationCriteria<String>(String.class, LENGTH_64, null, true, false));
        validationMap.put("mobile", new ValidationCriteria<String>(String.class, LENGTH_20, null, true, false));
        validationMap.put("ext", new ValidationCriteria<String>(String.class, LENGTH_500000, null, true, false));
        validationMap.put("dn", new ValidationCriteria<String>(String.class, LENGTH_500000, null, true, false));
        validationMap.put("custType", new ValidationCriteria<String>(String.class, LENGTH_32, null, true, false));
        validationMap.put("regFrom", new ValidationCriteria<String>(String.class, LENGTH_32, null, true, false));
        validationMap.put("extra", new ValidationCriteria<String>(String.class, LENGTH_16, null, true, false));
        validationMap.put("cancelReason", new ValidationCriteria<String>(String.class, LENGTH_16, null, true, false));
        validationMap.put("instCode", new ValidationCriteria<String>(String.class, LENGTH_32, null, true, false));
        validationMap.put("activateType", new ValidationCriteria<String>(String.class, LENGTH_16, null, true, false));
        validationMap.put("activateAppID", new ValidationCriteria<String>(String.class, LENGTH_16, null, true, false));


        validationMap.put("additionuvi", new ValidationCriteria<String>(String.class, LENGTH_500000, null, false, true));

        validationMap.put("authToken", new ValidationCriteria<String>(String.class, LENGTH_64, null, false, true));
        validationMap.put("authData", new ValidationCriteria<String>(String.class, LENGTH_500000, null, false, true));
        validationMap.put("scanData", new ValidationCriteria<String>(String.class, LENGTH_500000, null, true, false));

        validationMap.put("protocol", new ValidationCriteria<String>(String.class, LENGTH_16, null, false, true));

        validationMap.put("tenantId", new ValidationCriteria<String>(String.class, 128, null, false, true));

        validationMap.put("securityLevel", new ValidationCriteria<String>(String.class, 2, "^[1-3]$", false, true));

        return validationMap;
    }

    public static void validate(String parameterName, String parameterValue)
            throws PayloadValidationException {
        if("userName".equals(parameterName)){
            if(Strings.isNotBlank(parameterValue)){
                if(parameterValue.getBytes().length > USERNAME_LENGTH){
                    throw new PayloadValidationException("userName", VALIDATION_ERROR.INVALID_LENGTH);
                }
            }
        }
        ValidationCriteria<?> validationCriteria = validationMap.get(parameterName);
        if (validationCriteria != null) {
            VALIDATION_ERROR validationError = validate(validationCriteria, parameterValue);
            if (validationError != VALIDATION_ERROR.NONE)
                throw new PayloadValidationException(parameterName, validationError);
        } else {
            throw new IllegalArgumentException("Validation is not supported for - " + parameterName);
        }
    }
    public static void validate(String parameterName, List<String> parameterValue)throws PayloadValidationException{
        if(null == parameterValue || parameterValue.size() <= 0){
            throw new PayloadValidationException(parameterName,RestValidator.VALIDATION_ERROR.EMPTY);
        }
        for(String value : parameterValue){
            validate( parameterName, value);
        }
    }
    public static VALIDATION_ERROR validate(ValidationCriteria<?> validationCriteria, String value) {
        if (validationCriteria != null) {
            if (value == null) {
                if (validationCriteria.required) {
                    return VALIDATION_ERROR.EMPTY;
                }
                return VALIDATION_ERROR.NONE;
            }

            if ((value != null) && (value.trim().isEmpty())) {
                if (validationCriteria.allowEmptyValue) {
                    return VALIDATION_ERROR.NONE;
                }
                return VALIDATION_ERROR.EMPTY;
            }

            if (!validateLength(value, validationCriteria.maxLength)) {
                return VALIDATION_ERROR.INVALID_LENGTH;
            }

            if (!validateType(validationCriteria.type, value)) {
                return VALIDATION_ERROR.INVALID_TYPE;
            }

            if ((validationCriteria.restrictionRegex != null) && (!validateRestrictionRegex(validationCriteria.restrictionRegex, value))) {
                return VALIDATION_ERROR.INVALID_CHARACTERS;
            }
        } else {
            throw new IllegalArgumentException("Passed in ValidationCriteria argument is null for value - " + value);
        }

        return VALIDATION_ERROR.NONE;
    }

    public static boolean validateBoolean(String booleanValue) {
        return (booleanValue != null) && (!booleanValue.isEmpty()) && (
                (booleanValue.trim().equalsIgnoreCase("false")) || (booleanValue.trim().equalsIgnoreCase("true")));

    }

    private static boolean validateURL(String url) {
        URL urlObject = null;
        try {
            urlObject = new URL(url);
        } catch (MalformedURLException e) {
            return false;
        } finally {
            urlObject = null;
        }
        return true;
    }

    private static boolean validateURI(String uri) {
        try {
            new URI(uri);
        } catch (URISyntaxException e) {
            return false;
        }
        return true;
    }

    private static boolean isBase64Encoded(String parameterValue) {
        if ((parameterValue != null) && (!parameterValue.isEmpty())) {
            return Convert.isBase64(parameterValue);
        }
        return false;
    }

    private static boolean validateLength(String value, int length) {
        return !((length != -1) && ((value == null) || (value.trim().length() > length)));
    }

    private static boolean validateRestrictionRegex(String regex, String value) {
        Pattern r = Pattern.compile(regex);
        Matcher m = r.matcher(value);
        return m.find();
    }

    private static <T> boolean validateType(Class<T> type, String value) {
        if (type == Boolean.class)
            return validateBoolean(value);
        if (type == Base64.class)
            return isBase64Encoded(value);
        if (type == String.class)
            return true;
        if (type == Integer.class) {
            Integer.parseInt(value);
            return true;
        }
        if (type == URL.class)
            return validateURL(value);
        if (type == URI.class) {
            return validateURI(value);
        }
        throw new IllegalArgumentException("Validation is not supported for type - " + type);
    }

    public static RegistrationResponse compareRegRespVersions(List<RegistrationResponse> regResponses) {
        RegistrationResponse regResponse = null;
        Version previous = null;
        for (RegistrationResponse r : regResponses) {
            Version v = r.getOperationHeader().getProtocolVersion();
            if ((previous != null) && (v.compareTo(previous) == 0))
                throw new UAFRuntimeException("Two UAF responses can't have same version.");
            if (v.compareTo(new Version()) == 0) {
                regResponse = r;
            }
            previous = v;
        }
        return regResponse;
    }

    public static AuthenticationResponse compareAuthRespVersions(List<AuthenticationResponse> authResponses) {
        AuthenticationResponse authResponse = null;
        Version previous = null;
        for (AuthenticationResponse r : authResponses) {
            Version v = r.getOperationHeader().getProtocolVersion();
            if ((previous != null) && (v.compareTo(previous) == 0))
                throw new UAFRuntimeException("Two UAF responses can't have same version.");
            if (v.compareTo(new Version()) == 0) {
                authResponse = r;
            }
            previous = v;
        }
        return authResponse;
    }

    public enum VALIDATION_ERROR {
        NONE, EMPTY, INVALID_LENGTH, INVALID_TYPE, INVALID_CHARACTERS, DIFFERENCE, SUPPORT_ONE_TYPE
    }

    public static final int LENGTH_128 = 128;
    public static final int LENGTH_64 = 64;
    public static final int LENGTH_32 = 32;
    public static final int LENGTH_16 = 16;
    public static final int LENGTH_20 = 20;
    public static final int LENGTH_500000 = 500000;

    public static void validateDevices(DeviceDetails deviceDetails) throws PayloadValidationException {
        if (null != deviceDetails) {
            if (null != deviceDetails.getDeviceID() && deviceDetails.getDeviceID().length() > LENGTH_128) {
                throw new PayloadValidationException("deviceID", VALIDATION_ERROR.INVALID_LENGTH);
            }
            if (null != deviceDetails.getDeviceName() && deviceDetails.getDeviceName().length() > LENGTH_64) {
                throw new PayloadValidationException("deviceName", VALIDATION_ERROR.INVALID_LENGTH);
            }
            if (null != deviceDetails.getDeviceAliasName() && deviceDetails.getDeviceAliasName().length() > LENGTH_128) {
                throw new PayloadValidationException("deviceAliasName", VALIDATION_ERROR.INVALID_LENGTH);
            }
            if (null != deviceDetails.getDeviceType() && deviceDetails.getDeviceType().length() > LENGTH_64) {
                throw new PayloadValidationException("deviceType", VALIDATION_ERROR.INVALID_LENGTH);
            }
            if (null != deviceDetails.getOsType() && deviceDetails.getOsType().length() > LENGTH_16) {
                throw new PayloadValidationException("osType", VALIDATION_ERROR.INVALID_LENGTH);
            }
            if (null != deviceDetails.getOsVersion() && deviceDetails.getOsVersion().length() > LENGTH_64) {
                throw new PayloadValidationException("osVersion", VALIDATION_ERROR.INVALID_LENGTH);
            }
            if (null != deviceDetails.getImei() && deviceDetails.getImei().length() > LENGTH_64) {
                throw new PayloadValidationException("imei", VALIDATION_ERROR.INVALID_LENGTH);
            }
            if (null != deviceDetails.getMac() && deviceDetails.getMac().length() > LENGTH_64) {
                throw new PayloadValidationException("mac", VALIDATION_ERROR.INVALID_LENGTH);
            }
            if (null != deviceDetails.getDeviceVersion() && deviceDetails.getDeviceVersion().length() > LENGTH_64) {
                throw new PayloadValidationException("deviceVersion", VALIDATION_ERROR.INVALID_LENGTH);
            }
        }
    }
}
